#include "objectTerrain.h"

/*
 * This file base on the great tutorial of "www.videotutorialsrock.com"
 */

/****************************************************//**
 * Project-Namespace
 ********************************************************/
namespace jae
{

/****************************************************//**
 * Constructor of the class.
 ********************************************************/
ObjectTerrain::ObjectTerrain() :
    Object()
{
    _terrainCreated = false;
    _width = 0;
    _length = 0;
    _maxHeight = 10.0f;
    _heights = NULL;
    _normals = NULL;
    _textureIndex = 0;
    _textureLoaded = false;
    _scaleFactor = 1.0f;
    _tiling = false;
}

/****************************************************//**
 * Destructor of the class.
 ********************************************************/
ObjectTerrain::~ObjectTerrain()
{
    if(_terrainCreated)
    {
        for(int i = 0; i < _length; i++) 
        {
            delete[] _heights[i];
        }
        delete[] _heights;
        
        for(int i = 0; i < _length; i++) 
        {
            delete[] _normals[i];
        }
        delete[] _normals;
    }//if(_terrainCreated)
}

/****************************************************//**
 * This method sets a texture for this terrain.
 ********************************************************/
void ObjectTerrain::createColormap(const char* aFileName)
{  
    _textureIndex = _pTextureManager->addElem(aFileName);
    _textureLoaded = true;
}

/****************************************************//**
 * Creates the Terrain out of a heightmap (imagefile).
 * @param aMaxHeight from - aMaxHeight / 2 to aMaxHeight / 2
 * TODO umbenennen zu einheitlichem loadModelData
 ********************************************************/
void ObjectTerrain::createTerrain(const char* aFileName)
{   
    //Loading Image from file
    QImage imageFile;
    if (!imageFile.load(aFileName))
    {
        Logger logger;
        logger.error("ERROR",
                "void ObjectTerrain::createTerrain(const char aFilename)",
                "Can't load heightmap. File does not exist? / Unknown Fileformat?");
        logger.write("  aFilename: '");
        logger.write(aFileName);
        logger.write("'\n");
        return;
    }
    imageFile = QGLWidget::convertToGLFormat(imageFile);

    _width = imageFile.width();
    _length = imageFile.height();
    
    _heights = new float*[_length];
    for(int i = 0; i < _length; i++) 
    {
        _heights[i] = new float[_width];
    }
    
    _normals = new Vector3D*[_length];
    for(int i = 0; i < _length; i++) 
    {
        _normals[i] = new Vector3D[_width];
    }
    
    for(int y = 0; y < _length; y++) 
    {
        for(int x = 0; x < _width; x++) 
        {
            uchar color = imageFile.bits()[4 * (y * _width + x)];
            _heights[y][x] = _maxHeight * ((color / 255.0f) - 0.5f);
        }
    }
    
    calcNormals();
    
    _terrainCreated = true;
}

/****************************************************//**
 * Inline helpermethod to render the terrain. 
 ********************************************************/
inline void ObjectTerrain::renderTerrain()
{
    for (int z = 0; z < _length -1; z++)
    {
        glBegin(GL_TRIANGLE_STRIP);
        for (int x = 0; x <= _width; x++)
        {
            glTexCoord2f((float) x / _width, (float) z / _length);
            glNormal3f(_normals[z][x].x, _normals[z][x].y, _normals[z][x].z);
            glVertex3f(x*_scaleFactor, _heights[z][x], z*_scaleFactor);

            glTexCoord2f((float) x / _width, (float) (z + 1) / _length);
            glNormal3f(_normals[z+1][x].x, _normals[z+1][x].y, _normals[z+1][x].z);
            glVertex3f(x*_scaleFactor, _heights[z+1][x], (z+1)*_scaleFactor);
        }
        glEnd();
    }

    if (_visibilityNormals)
    {
        for (int z = 0; z < _length -1; z++)
        {

            //TODO normals zeichnen (muss einheitlich im Scenemanager einschaltbar sein

            glDisable(GL_LIGHTING);
            glColor3f(1.0f, 1.0f, 1.0f);
            glBegin(GL_LINES);
            for(int x = 0; x < _width; x++) 
            {
                Vector3D normal = _normals[z][x];

                glVertex3f(x*_scaleFactor, _heights[z][x], z*_scaleFactor);
                glVertex3f(x*_scaleFactor + normal.x, _heights[z][x] + normal.y, z*_scaleFactor + normal.z);
            }
            glEnd();
            glEnable(GL_LIGHTING);
        }
    }
}

/****************************************************//**
 * This Method renders the terrain. 
 * TODO variablenbezeichnungen anpassen
 ********************************************************/
void ObjectTerrain::renderModel()
{
    if(_terrainCreated)
    {
        //Static, damit sie nur einmal angelegt werden muessen;
        static bool bo_ColorMaterial;
        static bool bo_CullFace; //Gibt an, ob die Rueckseite weggeschnitten wird
        static bool bo_Texture2D;
        static bool bo_Lighting;
        static bool bo_Blending;

        bo_ColorMaterial = glIsEnabled(GL_COLOR_MATERIAL);
        bo_CullFace = glIsEnabled(GL_CULL_FACE);
        bo_Texture2D = glIsEnabled(GL_TEXTURE_2D);
        bo_Lighting = glIsEnabled(GL_LIGHTING);
        bo_Blending = glIsEnabled(GL_BLEND);
        
        glEnable(GL_LIGHTING);
        glEnable(GL_COLOR_MATERIAL); 
        
        if (_textureLoaded)
        {
            glEnable( GL_TEXTURE_2D );
            glBindTexture( GL_TEXTURE_2D, _textureIndex );
        }
        //Move to the Center of the terrain
        glTranslatef(-((float) _width*_scaleFactor)/2, 0.0f, -((float) _length*_scaleFactor)/2);
        
        glColor3f(1.0f, 1.0f, 1.0f);

        if (_tiling)
        {
            for (int movX = -1; movX <= 1; movX++)
                for (int movZ = -1; movZ <= 1; movZ++)
                {
                    glTranslatef(((float) _width*_scaleFactor*movX)
                            , 0.0f
                            , ((float) _length*_scaleFactor*movZ));
                    this->renderTerrain();   
                    glTranslatef(-((float) _width*_scaleFactor*movX)
                            , 0.0f
                            , -((float) _length*_scaleFactor*movZ));
                }
        }
        else
            this->renderTerrain();   
        
        //Move back
        glTranslatef(+((float) _width*_scaleFactor)/2, 0.0f, +((float) _length*_scaleFactor)/2);

        //TODO in protected object methode auslagern:
        if ( bo_Blending ) glEnable( GL_BLEND );
        else                glDisable( GL_BLEND );

        if ( bo_Texture2D ) glEnable( GL_TEXTURE_2D );
        else                glDisable( GL_TEXTURE_2D );

        if ( bo_ColorMaterial ) glEnable( GL_COLOR_MATERIAL );
        else                    glDisable( GL_COLOR_MATERIAL );

        if ( bo_Lighting ) glEnable( GL_LIGHTING );
        else               glDisable( GL_LIGHTING );

        if ( bo_CullFace ) glEnable( GL_CULL_FACE );
        else               glDisable( GL_CULL_FACE );
        
    }//if(_terrainCreated)
}


/****************************************************//**
 * Load Terrain (Heightmap) from a textfile (*.ter)\n
 * TODO this should be a XML-Parser\n
 * \n
 * Head of an Terrainfile:\n
 * <code>
 * # ParserVersion: 1.0
 * # 
 * # General Information:
 * # - the commands are case-sensitive!!!
 * # - don't use tabs instead of whitespaces
 * # - whitespaces are NOT optional
 * # - all numbers should be floating-point numbers
 * # - filenames should be relative to this file
 * #
 * # Command Help:
 * # "# ..." - Comment
 * # "HEIGHTMAP [Filename]" - Filename of an normal Imagefile
 * # "COLORMAP [Filename]" - Texture of the terrain
 * # "MAX_HEIGHT [value]" - Maximum height of the terrain
 * # "TILING [value] - 0 = Disabled, >1 = Count of tiles 
 * </code>
 ********************************************************/
bool ObjectTerrain::parseTerrainFile(const char* aFilename)
{  
    int lineNumber = 0;
    int paramCount;
    std::string line;                 // read a row
    std::string command;
    std::string param1;
    std::string param2;
    std::string param3;

    //Ermittle relative Pfadangabe:
    std::string folderName = splitFolderName(aFilename);
    
    //Lade file
    std::ifstream inFile(aFilename);    
    if (!inFile)
    {
        Logger logger;
        logger.error("ERROR","bool ObjectTerrain::parseTerrainFile(const char* aFilename, ObjectTerrain* aTerrainObject)",
                "Can't open file!");
        logger.write("  File: "); logger.write(aFilename); logger.write("\n");
        return false;
    }
           
    while (getline(inFile, line))
    {
        lineNumber++;

    //Entferne auf Wagenruecklaeufe: //TODO in helperfunktion auslagern
        size_t found = 0;
        while (found != line.npos)
        {
            found = line.find_first_of("\r", found);
            if (found != line.npos)
                line.erase(found, 1);
        }        

        if (line[0] != '#' and !line.empty())   // comment or empty
        {

            //Parse Zeile
            paramCount = -1;
            paramCount += getNextParam(&line, &command); //paramCount = 0
            paramCount += getNextParam(&line, &param1);
            paramCount += getNextParam(&line, &param2);
            paramCount += getNextParam(&line, &param3);
            
            if (command == "HEIGHTMAP")
            {
                std::string tmp = folderName + "/" + param1;
                this->createTerrain(tmp.c_str());
                checkLineSyntax(aFilename, lineNumber, command, paramCount, 1);
            }
            else if (command == "COLORMAP")
            {
                std::string tmp = folderName + "/" + param1;
                this->createColormap(tmp.c_str());
                checkLineSyntax(aFilename, lineNumber, command, paramCount, 1);
            }
            else if (command == "MAX_HEIGHT")
            {
                this->setMaxHeight(atof(param1.c_str()));
                checkLineSyntax(aFilename, lineNumber, command, paramCount, 1);
            }
            else if (command == "TILING")
            {
                if (param1 == "TRUE")
                    _tiling = true;
                else
                    _tiling = false;
                
                checkLineSyntax(aFilename, lineNumber, command, paramCount, 1);
            }
            else
            {
                Logger logger;
                logger.error("WARNING", "bool ObjectTerrain::parseTerrainFile(const char* aFilename, ObjectTerrain* aTerrainObject)",
                        "Unknown command!");
                logger.write("  File: "); logger.write(aFilename);
                logger.write(":"); logger.write(lineNumber);
                logger.write("\n  Command: \""); logger.write(command);
                logger.write("\"\n");
            }
            
        } //if not comment or empty
    }//while has next line

    inFile.close();

    return true;
}


/****************************************************//**
 * Parse a string and gets the next parameter.
 ********************************************************/
inline bool ObjectTerrain::getNextParam(std::string* aLine, std::string* aNextParam)
{   
    size_t first = aLine->find_first_not_of(" ");
    size_t second = aLine->find_first_of(" ", first);
    
    if (first == aLine->npos)
    {
        *aNextParam = "";
        return false;
    }
        
    *aNextParam = aLine->substr(first, second - first);
    aLine->erase(0, second);
    return true;
}

/****************************************************//**
 * Check the number of parameter and log warnings.
 ********************************************************/
inline void ObjectTerrain::checkLineSyntax(const char* aFilename, int lineNumber, 
        std::string& aCommand, int aParamCount, int aExpectedCount)
{
    if (aParamCount != aExpectedCount)
    {
        Logger logger;
        logger.error("WARNING", "bool ObjectTerrain::parseTerrainFile(const char* aFilename, ObjectTerrain* aTerrainObject)",
                "Wrong number of arguments!");
        logger.write("  File: "); logger.write(aFilename);
        logger.write(":"); logger.write(lineNumber); logger.write("\n");
        logger.write("  Command: "); logger.write(aCommand);
        logger.write(" expected: "); logger.write(aExpectedCount);
        logger.write(" got: >="); logger.write(aParamCount); logger.write("\n");
    }
}

/****************************************************//**
 * Computes the normals. 
 ********************************************************/
inline void ObjectTerrain::calcNormals()
{
    Vector3D** helper = new Vector3D*[_length];
    for(int i = 0; i < _length; i++) 
    {
        helper[i] = new Vector3D[_width];
    }
    
    for(int z = 0; z < _length; z++) 
    {
        for(int x = 0; x < _width; x++) 
        { 
            Vector3D sum = vectorCreate3D(0.0f, 0.0f, 0.0f);
            Vector3D out = vectorCreate3D(0.0f, 0.0f, 0.0f);
            
            if (z > 0) {
                out = vectorCreate3D(0.0f, _heights[z - 1][x] - _heights[z][x], -1.0f);
            }
            Vector3D in = vectorCreate3D(0.0f, 0.0f, 0.0f);
            if (z < _length - 1) {
                in = vectorCreate3D(0.0f, _heights[z + 1][x] - _heights[z][x], 1.0f);
            }
            Vector3D left = vectorCreate3D(0.0f, 0.0f, 0.0f);
            if (x > 0) {
                left = vectorCreate3D(-1.0f, _heights[z][x - 1] - _heights[z][x], 0.0f);
            }
            Vector3D right = vectorCreate3D(0.0f, 0.0f, 0.0f);
            if (x < _width - 1) {
                right = vectorCreate3D(1.0f, _heights[z][x + 1] - _heights[z][x], 0.0f);
            }
            
            if (x > 0 && z > 0) 
            {
                sum = sum + vectorNormalize(vectorCrossProduct(&out, &left));
            }
            if (x > 0 && z < _length - 1) 
            {
                sum = sum + vectorNormalize(vectorCrossProduct(&left, &in));
            }
            if (x < _width - 1 && z < _length - 1) 
            {
                sum = sum + vectorNormalize(vectorCrossProduct(&in, &right));
            }
            if (x < _width - 1 && z > 0) 
            {
                sum = sum + vectorNormalize(vectorCrossProduct(&right, &out));
            }
            
            helper[z][x] = sum;
        }
    }
    
    //Smooth out the normals
    const float SMOOTH_FACTOR = 0.5f;
    for(int z = 0; z < _length; z++) 
    {
        for(int x = 0; x < _width; x++) 
        {
            Vector3D sum = helper[z][x];
            
            if (x > 0) 
            {
                sum = sum + helper[z][x - 1] * SMOOTH_FACTOR;
            }
            if (x < _width - 1) 
            {
                sum = sum + helper[z][x + 1] * SMOOTH_FACTOR;
            }
            if (z > 0) 
            {
                sum = sum + helper[z - 1][x] * SMOOTH_FACTOR;
            }
            if (z < _length - 1) 
            {
                sum = sum + helper[z + 1][x] * SMOOTH_FACTOR;
            }
            
            if (vectorGetLength(&sum) == 0) {
                sum = vectorCreate3D(0.0f, 1.0f, 0.0f);
            }
            _normals[z][x] = vectorNormalize(sum);
        }
    }
    
    for(int i = 0; i < _length; i++) {
        delete[] helper[i];
    }
    delete[] helper;
}

/****************************************************//**
 * This method changes the heightvalues of the terrain 
 * and sets the maximum height.
 ********************************************************/
void ObjectTerrain::setMaxHeight(float aValue)
{
    if (aValue == 0.0f)
    {
        Logger logger;
        logger.error("ERROR", "void ObjectTerrain::setMaxHeight(float aValue)",
                "MaxHeight shouldn't be set to 0.0f");
        return;
    }
    
    //Set new Height
    if(_terrainCreated)
    {
        for (int z = 0; z < _length; z++)
        {
            for (int x = 0; x < _width; x++)
            {
                _heights[z][x] *= aValue / _maxHeight;
            }
        }
    }
    
    //Set Variable
    _maxHeight = aValue;
}

/****************************************************//**
 * Sets the scale factor of an Object (=Size).
 ********************************************************/
void ObjectTerrain::setScaleFactor(GLfloat aValue)
{
    if(aValue == 0.0f)
    {
        Logger logger;
        logger.error("ERROR", 
                "void ObjectTerrain::setScaleFactor(GLfloat aValue)",
                "A ScaleFactor of 0.0f is not possible! (Use method hide() instead)");
    }
    
    for (int z = 0; z < _length; z++)
    {
        for (int x = 0; x < _width; x++)
        {
            _heights[z][x] *= aValue / _scaleFactor;
        }
    }
    _scaleFactor = aValue;
    this->calcNormals();

    if(this->hasDisplayList())
    {
        this->deleteDisplayList();
        this->createDisplayList();
    }
}

/****************************************************//**
 * @return Returns the scale factor of an Object (=Size).
 ********************************************************/
GLfloat ObjectTerrain::getScaleFactor()
{
    return _scaleFactor;
}

/****************************************************//**
 * Returns the maximum Height of the terrain!
 ********************************************************/
float ObjectTerrain::getMaxHeight()
{
    return _maxHeight;
}

} //jae Namespace
